from sqlalchemy import and_, select
from sqlalchemy.exc import DBAPIError, IntegrityError
from sqlalchemy.orm import joinedload

from src.core.exceptions.common import RepoError
from src.core.models.dto import CalendarDTO, UserCalendarDTO
from src.core.models.enums import SubscriptionStatus, UserCalendarStatus
from src.database.converters.calendar import (
    convert_db_model_to_calendar_dto,
    convert_db_model_to_calendar_with_tasks_and_messages_dto,
    convert_db_model_to_user_calendar_dto,
)
from src.database.exception_mapper import exception_mapper
from src.database.models import Calendar, CalendarTask, Subscription, User, UserCalendar
from src.database.repositories.base import SQLAlchemyRepo


class CalendarReaderImpl(SQLAlchemyRepo):
    @exception_mapper
    async def get_calendar_by_id(self, calendar_id: int) -> CalendarDTO | None:
        """Получаем календарь с заданиями по id"""

        query = (
            select(Calendar)
            .outerjoin(Calendar.tasks)
            .where(
                Calendar.id == calendar_id,
            )
            .order_by(CalendarTask.day.asc())
            .options(joinedload(Calendar.tasks), joinedload(Calendar.check_messages))
        )

        calendar: Calendar | None = await self._session.scalar(query)
        calendar.tasks.sort(key=lambda x: x.day)

        if not calendar:
            return None

        return convert_db_model_to_calendar_with_tasks_and_messages_dto(calendar)

    @exception_mapper
    async def get_user_calendar_by_id(
        self, user_calendar_id: int, subscription_id: int
    ) -> UserCalendarDTO | None:
        """Получаем календарь пользователя"""

        calendar: UserCalendar | None = await self._session.scalar(
            select(UserCalendar)
            .where(
                and_(
                    UserCalendar.id == user_calendar_id,
                    UserCalendar.subscription_id == subscription_id,
                )
            )
            .options(joinedload(UserCalendar.calendar))
        )

        if not calendar:
            return None

        return convert_db_model_to_user_calendar_dto(calendar)

    @exception_mapper
    async def get_calendars_in_category(self, category_id: int) -> list[CalendarDTO]:
        """Получаем календари в категории"""

        res = await self._session.scalars(
            select(Calendar).where(Calendar.category_id == category_id)
        )
        calendars: list[Calendar] = list(res)

        return [convert_db_model_to_calendar_dto(calendar) for calendar in calendars]

    @exception_mapper
    async def get_user_calendars(self, user_tg_id: int) -> list[UserCalendarDTO]:
        """Получаем календари в категории"""

        res = await self._session.scalars(
            select(UserCalendar)
            .join(UserCalendar.user)
            .join(UserCalendar.subscription)
            .where(
                and_(
                    User.telegram_id == user_tg_id,
                    Subscription.status == SubscriptionStatus.active,
                )
            )
            .options(joinedload(UserCalendar.calendar))
        )
        calendars: list[UserCalendar] = list(res.unique())

        return [
            convert_db_model_to_user_calendar_dto(calendar) for calendar in calendars
        ]

    @exception_mapper
    async def get_active_users_calendars(self) -> list[UserCalendarDTO]:
        """Получаем активные календари пользователей"""

        res = await self._session.scalars(
            select(UserCalendar)
            .join(UserCalendar.subscription)
            .where(
                and_(
                    UserCalendar.status == UserCalendarStatus.active,
                    Subscription.status == SubscriptionStatus.active,
                )
            )
            .options(joinedload(UserCalendar.calendar))
        )
        calendars: list[UserCalendar] = list(res.unique())

        return [
            convert_db_model_to_user_calendar_dto(calendar) for calendar in calendars
        ]


class CalendarRepoImpl(SQLAlchemyRepo):
    @exception_mapper
    async def acquire_user_calendar_by_id(
        self, user_calendar_id: int
    ) -> UserCalendar | None:
        """Получаем календарь пользователя"""

        calendar: UserCalendar | None = await self._session.scalar(
            select(UserCalendar).where(
                UserCalendar.id == user_calendar_id,
            )
        )

        return calendar

    @exception_mapper
    async def create_calendar(self, calendar: UserCalendar) -> None:
        """Создаем календарь пользователя"""

        self._session.add(calendar)
        try:
            await self._session.flush((calendar,))
        except IntegrityError as err:
            self._parse_error(err)

    @exception_mapper
    async def update_calendar(self, calendar: UserCalendar) -> None:
        """Обновляем календарь пользователя"""

        try:
            await self._session.merge(calendar)
        except IntegrityError as err:
            self._parse_error(err)

    @staticmethod
    def _parse_error(err: DBAPIError) -> None:
        raise RepoError from err
